# nim nativesockets wrapper like gonet used for goroutine
# All functions are no raise, only return value

{.passc:"-g -O0".}
import nativesockets
import net
import strutils
import tables

import nimlog
import nimplus

# newSocket
# dial
# read
# write
# close
# setDeadline
# setReadDeadline
# setWriteDeadline
# readline()

const ortcp* = "tcp"
const orudp* = "udp"
const orunix* = "unix"

proc `$`(sock:SocketHandle) : string = sock.repr.strip()
proc `$`(sock:Socket) : string = $(sock.getFd())

const ADDR_ANY = "0.0.0.0"
# host address format: [host|*]:port
proc splithostport(address: string) : (string, Port, error) =
    let segs = address.split(":")
    if segs.len != 2:
        result[2] = newError("Invalid address format: " & address)
        return
    var ipaddr = segs[0]
    var portstr = segs[1]
    if ipaddr == "" or ipaddr == "*": ipaddr = ADDR_ANY
    if portstr == "": portstr = "0"
    return (ipaddr, portstr.parseInt().Port, nil)

proc dial*(network, address: string) : (Socket, error) =
    let segs = address.split(":")
    let ipaddr = segs[0]
    let portstr = segs[1]
    try:
        let port = parseInt(portstr)
        let proto = {ortcp: IPPROTO_TCP, orudp: IPPROTO_UDP}.toTable[network]
        var sock = net.dial(ipaddr, port.Port, protocol=proto, buffered = false)
        result = (sock, nil)
    except:
        result = (nil, newError(getCurrentExceptionMsg()))
    return


# var buf = newStringOfCap(128)
proc read*(sock:Socket, buf: var string) : (int, error) =
    try:
        doAssert(buf.cap() > 0)
        var rv = sock.recv(buf, buf.cap())
        result = (rv, nil)
    except:
        result = (0, newError(getCurrentExceptionMsg()))
    return

proc write*(sock:Socket, buf: string) : (int, error) {.discardable.} =
    try:
        sock.send(buf)
        result = (buf.len, nil)
    except:
        result = (0, newError(getCurrentExceptionMsg()))
    return

proc readline*(sock:Socket) : (string, error) =
    try:
        var linestr = sock.recvLine()
        result = (linestr, nil)
    except:
        result = ("", newError(getCurrentExceptionMsg()))
    return

proc listen*(network, address : string) : (Socket, error) =
    var sock = newSocket(buffered=false)
    try:
        let (ipaddr, port, err) = splithostport(address)
        if err.sure:
            result[1] = err.wrap("listen failed")
            return
        sock.setSockOpt(OptReuseAddr, true)
        sock.setSockOpt(OptReusePort, true)
        sock.bindAddr(port, ipaddr)
        sock.listen()
        result[0] = sock
    except:
        result[1] = newErrorExc()
    return

proc accept*(sock:Socket):(Socket, error) =
    try:
        var conn : Socket
        sock.accept(conn)
        result[0] = conn
    except:
        result[1] = newErrorExc()
    return

proc test_ornet_cli0() =
    var rv1 = dial(ortcp, "www.baidu.com:80")
    linfo rv1
    var rv2 = rv1[0].write("GET / HTTP/1.1\r\n\r\n")
    linfo rv2
    var buf = newStringOfCap(123)
    var rv3 = rv1[0].read(buf)
    linfo rv3
    var rv4 = rv1[0].readline()
    linfo rv4

proc test_ornet_srv0() =
    var rv5 = listen(ortcp, "*:5500")
    linfo rv5, rv5[1].okay
    if rv5[1] != nil: linfo rv5[1].detail()
    while true:
        var rv6 = rv5[0].accept()
        linfo rv6

if isMainModule:
    var t1 = (1,2,"3")
    # echo t1.tail1
    #test_ornet_srv0()

{.push hint[XDeclaredButNotUsed]:off.}
